#include <algorithm>
#include <iostream>
#include <chrono>
#include <cmath>
#include <fstream>
#include <string>
#include <thread>
#include <numeric>

struct ProfileResult
{
	std::string name;
	long long start, end;
	uint32_t thread_id;

};

struct InstrumentationSession
{
	std::string name;
};

class Instrumentor
{
private:
	InstrumentationSession* current_session;
	std::ofstream output_stream;
	int profile_count;
public:
	Instrumentor()
		: current_session(nullptr), profile_count(0)
	{
	}

	void begin_session(const std::string& name, const std::string& filepath = "results.json")
	{
		output_stream.open(filepath);
		write_header();
		current_session = new InstrumentationSession{ name };
	}

	void end_session()
	{
		write_footer();
		output_stream.close();
		delete current_session;
		current_session = nullptr;
		profile_count = 0;
	}

	void write_profile(const ProfileResult& result)
	{
		if (profile_count++ > 0)
			output_stream << ",";

		std::string name = result.name;
		std::replace(name.begin(), name.end(), '"', '\'');
		output_stream << "{";
		output_stream << "\"car\":\"function\",";
		output_stream << "\"dur\":" << (result.end - result.start) << ',';
		output_stream << "\"name\":\"" << name << "\",";
		output_stream << "\"ph\":\"X\",";
		output_stream << "\"pid\":0,";
		output_stream << "\"tid\":" << result.thread_id << ",";
		output_stream << "\"ts\":" << result.start;
		output_stream << "}";

		output_stream.flush();
	}

	void write_header()
	{
		output_stream << "{\"otherData\":{},\"traceEvents\":[";
		output_stream.flush();
	}

	void write_footer()
	{
		output_stream << "]}";
		output_stream.flush();
	}

	static Instrumentor& get()
	{
		static Instrumentor instance;
		return instance;
	}
};

class InstrumentationTimer
{
private:
	const char* name;
	std::chrono::time_point<std::chrono::steady_clock> start_time_point;
	bool stoped;
public:
	InstrumentationTimer(const char* name)
		: name(name), stoped(false)
	{
		start_time_point = std::chrono::high_resolution_clock::now();
	}
	~InstrumentationTimer()
	{
		if (!stoped)
			stop();
	}
	void stop()
	{
		auto end_time_point = std::chrono::high_resolution_clock::now();
		long long start = std::chrono::time_point_cast<std::chrono::microseconds>(start_time_point).time_since_epoch().count();
		long long end = std::chrono::time_point_cast<std::chrono::microseconds>(end_time_point).time_since_epoch().count();

		uint32_t thread_id = std::hash<std::thread::id>{}(std::this_thread::get_id());

		Instrumentor::get().write_profile({ name, start, end, thread_id });
		stoped = true;
	}
};

#define PROFILE 1
#if PROFILE 
#define PROFILE_SCOPE(name) InstrumentationTimer timer##__LINE__(name)
#define PROFILE_FUNCTION() PROFILE_SCOPE(__FUNCSIG__)
#else
#define PROFILE_SCOPE(name)  
#endif

void function(int value)
{
	PROFILE_FUNCTION();
	for (int i = 0; i < 10000; ++i) {
		std::cout << "hello world #" << i + value << std::endl;
	}
}

void function()
{
	PROFILE_FUNCTION();
	for (int i = 0; i < 10000; ++i) {
		std::cout << "hello world #" << i << std::endl;
	}
}

void run_benchmarks()
{
	PROFILE_FUNCTION();
	std::cout << "Running Benchmarks...\n";
	function(1);
	function();
}

int main()
{
	Instrumentor::get().begin_session("profile");
	run_benchmarks();
	Instrumentor::get().end_session();
	return 0;
}